#define JMBC_INSIDE
#include "mlMaterial.h"
#include <Math/mlMath.h>
#include <Core/clCore.h>
#include <Image/ilImage.h>
#include <stdio.h>
#include <string.h>
#include <GL/glu.h>

#define matg(g) material->groups[ML_MATERIAL_GROUP_ ## g]
#define mats(t) sizeof(MLMaterial ## t)

// Context

#define STORE ID_SMALL_STORE

static MLMaterial *store[STORE];

void
mlMaterialCreateContext(void)
{
	idClearStore(MLMaterial, STORE, store);
}

// Generation / Deletion

static
void
Init(MLMaterial *material)
{
	material->action = material->frame = 0;
}

static
void
Term(MLMaterial *material)
{
}

void
mlGenMaterials(unsigned int n, unsigned int *materials)
{
	idGenObjects(MLMaterial, STORE, store, Init, n, materials);
}

void
mlDeleteMaterials(unsigned int n, unsigned int *materials)
{
	idDeleteObjects(store, Term, n, materials);
}

// Load / Unload

void
mlMaterialLoad(unsigned int id, unsigned int filepath)
{
	FILE *stream;
	char tmp[256];

	//stream = _wfopen(tlStringData(filepath), L"rb");
	wcstombs(tmp, tlStringData(filepath), 256);
	stream = fopen(tmp, "rb");

	if(stream)
	{
		MLMaterial *material;
		MLMaterialHeader header;

		material = store[id];

		fread(&header, 1, mats(Header), stream);

		tlGenGroups(3, material->groups);

		tlGroupRead(matg(PROPERTIES), 1, mats(Properties), stream);
		tlGroupRead(matg(FRAMES), header.frames, mats(Frame), stream);
		tlGroupRead(matg(ACTIONS), header.actions, mats(Action), stream);

		fclose(stream);
	}
	else
		elErrorSet(EL_ERROR_INVALID);
}

void
mlMaterialUnload(unsigned int id)
{
	MLMaterial *material;

	material = store[id];

	tlDeleteGroups(3, material->groups);
}

void
mlMaterialsUnload(unsigned int n, unsigned int *materials)
{
	unsigned int i;

	for(i = 0; i < n; i++)
		mlMaterialUnload(materials[i]);
}

// Operation

void
mlMaterialCompile(unsigned int id, unsigned int filepath)
{
	FILE *stream;
	char tmp[256];

	//stream = _wfopen(tlStringData(filepath), L"r");
	wcstombs(tmp, tlStringData(filepath), 256);
	stream = fopen(tmp, "r");

	if(stream)
	{
		MLMaterial *material;
		MLMaterialHeader header;
		MLMaterialProperties *properties;
		MLMaterialFrame *frames;
		MLMaterialAction *actions;
		wchar_t buffer[512];
		unsigned int i;

		material = store[id];

		tlGenGroups(3, material->groups);

		fgetws(buffer, 512, stream);
		swscanf(buffer, L"properties");

		wprintf(L"Properties\n");

		tlGroupAllocate(matg(PROPERTIES), 1, mats(Properties));
		properties = tlGroupArray(matg(PROPERTIES));

		for(i = 0; i < 1; i++)
		{
			fgetws(buffer, 512, stream);
			memset(properties[i].texture, 0, sizeof(wchar_t) * TL_STRING_DEFAULT_FIXED_SIZE);
			wcsncpy(properties[i].texture, buffer + wcslen(L"texture "), wcslen(buffer) - wcslen(L"texture ") - 1);
			properties[i].texture[wcslen(buffer) - wcslen(L"texture ") - 1] = L'\0';

			fgetws(buffer, 512, stream);
			swscanf(buffer, L"alpha %f", &properties[i].alpha);

			fgetws(buffer, 512, stream);
			swscanf(buffer, L"ambient %f %f %f", &properties[i].ambient[0], &properties[i].ambient[1], &properties[i].ambient[2]);

			fgetws(buffer, 512, stream);
			swscanf(buffer, L"diffuse %f %f %f", &properties[i].diffuse[0], &properties[i].diffuse[1], &properties[i].diffuse[2]);

			fgetws(buffer, 512, stream);
			swscanf(buffer, L"specular %f %f %f", &properties[i].specular[0], &properties[i].specular[1], &properties[i].specular[2]);

			fgetws(buffer, 512, stream);
			swscanf(buffer, L"emission %f %f %f", &properties[i].emission[0], &properties[i].emission[1], &properties[i].emission[2]);

			fgetws(buffer, 512, stream);
			swscanf(buffer, L"shininess %f", &properties[i].shininess);
		}

		fgetws(buffer, 512, stream);
		swscanf(buffer, L"frames %i", &header.frames);

		wprintf(L"Frames: %i\n", header.frames);

		tlGroupAllocate(matg(FRAMES), header.frames, mats(Frame));
		frames = tlGroupArray(matg(FRAMES));

		for(i = 0; i < header.frames; i++)
		{
			fgetws(buffer, 512, stream);
			swscanf(buffer, L"matrix %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f", &frames[i].matrix[0], &frames[i].matrix[1], &frames[i].matrix[2], &frames[i].matrix[3], &frames[i].matrix[4], &frames[i].matrix[5], &frames[i].matrix[6], &frames[i].matrix[7], &frames[i].matrix[8], &frames[i].matrix[9], &frames[i].matrix[10], &frames[i].matrix[11], &frames[i].matrix[12], &frames[i].matrix[13], &frames[i].matrix[14], &frames[i].matrix[15]);
		}

		fgetws(buffer, 512, stream);
		swscanf(buffer, L"actions %i", &header.actions);

		wprintf(L"Actions: %i\n", header.actions);

		tlGroupAllocate(matg(ACTIONS), header.actions, mats(Action));
		actions = tlGroupArray(matg(ACTIONS));

		for(i = 0; i < header.actions; i++)
		{
			fgetws(buffer, 512, stream);
			memset(actions[i].name, 0, sizeof(wchar_t) * TL_STRING_DEFAULT_FIXED_SIZE);
			wcsncpy(actions[i].name, buffer + wcslen(L"name "), wcslen(buffer) - wcslen(L"name ") - 1);
			actions[i].name[wcslen(buffer) - wcslen(L"name ") - 1] = L'\0';

			fgetws(buffer, 512, stream);
			swscanf(buffer, L"start %i", &actions[i].start);

			fgetws(buffer, 512, stream);
			swscanf(buffer, L"frames %i", &actions[i].frames);
		}

		fclose(stream);
	}
	else
		elErrorSet(EL_ERROR_INVALID);
}

void
mlMaterialDecompile(unsigned int id, unsigned int filepath)
{
	FILE *stream;
	char tmp[256];

	//stream = _wfopen(tlStringData(filepath), L"w");
	wcstombs(tmp, tlStringData(filepath), 256);
	stream = fopen(tmp, "w");

	if(stream)
	{
		MLMaterial *material;
		MLMaterialHeader header;
		MLMaterialProperties *properties;
		MLMaterialFrame *frames;
		MLMaterialAction *actions;
		unsigned int i;

		material = store[id];

		properties = tlGroupArray(matg(PROPERTIES));

		fwprintf(stream, L"properties\n");

		for(i = 0; i < 1; i++)
		{
			fwprintf(stream, L"texture %s\n", properties[i].texture);

			fwprintf(stream, L"alpha %f\n", properties[i].alpha);

			fwprintf(stream, L"ambient %f %f %f\n", properties[i].ambient[0], properties[i].ambient[1], properties[i].ambient[2]);

			fwprintf(stream, L"diffuse %f %f %f\n", properties[i].diffuse[0], properties[i].diffuse[1], properties[i].diffuse[2]);

			fwprintf(stream, L"specular %f %f %f\n", properties[i].specular[0], properties[i].specular[1], properties[i].specular[2]);

			fwprintf(stream, L"emission %f %f %f\n", properties[i].emission[0], properties[i].emission[1], properties[i].emission[2]);

			fwprintf(stream, L"shininess %f\n", properties[i].shininess);
		}

		header.frames = tlGroupLength(matg(FRAMES));
		frames = tlGroupArray(matg(FRAMES));

		fwprintf(stream, L"frames %i\n", header.frames);

		for(i = 0; i < header.frames; i++)
		{
			fwprintf(stream, L"matrix %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f\n", frames[i].matrix[0], frames[i].matrix[1], frames[i].matrix[2], frames[i].matrix[3], frames[i].matrix[4], frames[i].matrix[5], frames[i].matrix[6], frames[i].matrix[7], frames[i].matrix[8], frames[i].matrix[9], frames[i].matrix[10], frames[i].matrix[11], frames[i].matrix[12], frames[i].matrix[13], frames[i].matrix[14], frames[i].matrix[15]);
		}

		header.actions = tlGroupLength(matg(ACTIONS));
		actions = tlGroupArray(matg(ACTIONS));

		fwprintf(stream, L"actions %i\n", header.actions);

		for(i = 0; i < header.actions; i++)
		{
			fwprintf(stream, L"name %s\n", actions[i].name);

			fwprintf(stream, L"start %i\n", actions[i].start);

			fwprintf(stream, L"frames %i\n", actions[i].frames);
		}

		fclose(stream);
	}
	else
		elErrorSet(EL_ERROR_INVALID);
}

unsigned int
mlMaterialFrames(unsigned int id)
{
	MLMaterial *material;

	material = store[id];

	return tlGroupLength(matg(FRAMES));
}

unsigned int
mlMaterialActions(unsigned int id)
{
	MLMaterial *material;

	material = store[id];

	return tlGroupLength(matg(ACTIONS));
}

unsigned int
mlMaterialAction(unsigned int id, unsigned int name)
{
	MLMaterial *material;
	MLMaterialAction *actions;
	unsigned int i;

	material = store[id];

	actions = tlGroupArray(matg(ACTIONS));
	for(i = 0; wcscmp(actions[i].name, tlStringData(name)); i++);

	return i;
}

unsigned int
mlMaterialActionFrames(unsigned int id, unsigned int action)
{
	MLMaterial *material;
	MLMaterialAction *actions;

	material = store[id];

	actions = tlGroupArray(matg(ACTIONS));

	return actions[action].frames;
}

void
mlMaterialActionSet(unsigned int id, unsigned int action)
{
	MLMaterial *material;

	material = store[id];

	material->action = action;
}

void
mlMaterialActionFrameSet(unsigned int id, unsigned int frame)
{
	MLMaterial *material;

	material = store[id];

	material->frame = frame;
}

float *
mlMaterialMatrix(unsigned int id)
{
	MLMaterial *material;
	MLMaterialFrame *frames;
	MLMaterialAction *actions;

	material = store[id];

	frames = tlGroupArray(matg(FRAMES));
	actions = tlGroupArray(matg(ACTIONS));

	return frames[actions[material->action].start + material->frame].matrix;
}

/*
float
mlMaterialAlpha(unsigned int id)
{
	MLMaterial *material;
	MLMaterialProperties *properties;

	material = store[id];

	properties = tlGroupArray(matg(PROPERTIES));

	return properties[0].alpha;
}
*/

// Disk I/O

void
mlMaterialSave(unsigned int id, unsigned int filepath)
{
	FILE *stream;
	char tmp[256];

	//stream = _wfopen(tlStringData(filepath), L"wb");
	wcstombs(tmp, tlStringData(filepath), 256);
	stream = fopen(tmp, "wb");

	if(stream)
	{
		MLMaterial *material;
		MLMaterialHeader header;

		material = store[id];

		header.frames = tlGroupLength(matg(FRAMES));
		header.actions = tlGroupLength(matg(ACTIONS));

		fwrite(&header, 1, mats(Header), stream);

		tlGroupWrite(matg(PROPERTIES), stream);
		tlGroupWrite(matg(FRAMES), stream);
		tlGroupWrite(matg(ACTIONS), stream);

		fclose(stream);
	}
	else
		elErrorSet(EL_ERROR_INVALID);
}

// OpenGL Integration

void
mlMaterialGenTextures(unsigned int id, unsigned int path)
{
	MLMaterial *material;
	MLMaterialProperties *src;
	GLuint *dst;
	unsigned int strings[3];
	unsigned int image;
	unsigned int i;

	material = store[id];

	src = tlGroupArray(matg(PROPERTIES));
	tlGenGroups(1, &material->texture);
	tlGroupAllocate(material->texture, tlGroupLength(matg(PROPERTIES)), sizeof(GLuint));
	dst = tlGroupArray(material->texture);
	glGenTextures(tlGroupLength(matg(PROPERTIES)), dst);

	tlGenStrings(2, &strings[1]);
	strings[0] = path;

	ilGenImages(1, &image);

	for(i = 0; i < tlGroupLength(matg(PROPERTIES)); i++)
	{
		glBindTexture(GL_TEXTURE_2D, dst[i]);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		tlStringLoad(strings[1], src[i].texture);
		tlStringConcatPath(strings[2], 2, strings);
		ilImageLoad(image, strings[2]);
		tlStringsUnload(2, &strings[1]);
		//ilImageTexImage2D(image);
		ilImageBuild2DMipmaps(image);
		ilImageUnload(image);
	}

	ilDeleteImages(1, &image);

	tlDeleteStrings(2, &strings[1]);
}

void
mlMaterialDeleteTextures(unsigned int id)
{
	MLMaterial *material;

	material = store[id];

	glDeleteTextures(tlGroupLength(material->texture), tlGroupArray(material->texture));
	tlGroupUnload(material->texture);
	tlDeleteGroups(1, &material->texture);
}

void
mlMaterialBind(unsigned int id)
{
	MLMaterial *material;
	unsigned int *texture;
	MLMaterialProperties *properties;
	MLMaterialFrame *frames;
	MLMaterialAction *actions;

	material = store[id];

	texture = tlGroupArray(material->texture);
	properties = tlGroupArray(matg(PROPERTIES));
	frames = tlGroupArray(matg(FRAMES));
	actions = tlGroupArray(matg(ACTIONS));

	{
		float ambient[4] =
		{
			properties[0].ambient[0],
			properties[0].ambient[1],
			properties[0].ambient[2],
			properties[0].alpha
		};
		float diffuse[4] =
		{
			properties[0].diffuse[0],
			properties[0].diffuse[1],
			properties[0].diffuse[2],
			properties[0].alpha
		};
		float specular[4] =
		{
			properties[0].specular[0],
			properties[0].specular[1],
			properties[0].specular[2],
			properties[0].alpha
		};
		float emission[4] =
		{
			properties[0].emission[0],
			properties[0].emission[1],
			properties[0].emission[2],
			properties[0].alpha
		};

		glBindTexture(GL_TEXTURE_2D, texture[0]);
		glMaterialfv(GL_FRONT, GL_AMBIENT, ambient);
		glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse);
		glMaterialfv(GL_FRONT, GL_SPECULAR, specular);
		glMaterialfv(GL_FRONT, GL_EMISSION, emission);
		glMaterialf(GL_FRONT, GL_SHININESS, properties[0].shininess);
	}
}
